package org.trc.network;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JFileChooser;

import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.shapefile.shp.ShapefileException;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.swing.data.JFileDataStoreChooser;

import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateFilter;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.linearref.LengthIndexedLine;
import com.vividsolutions.jts.operation.linemerge.LineMerger;


/**
 * 
 * @author Lin Xu
 *
 */


public class Network {
    public Map<String, Node> nodeCollection = Collections.synchronizedMap(new HashMap<String, Node>()); //节点集合(拓扑)
    public Map<String, Edge> edgeCollection = Collections.synchronizedMap(new HashMap<String, Edge>()); //路段集合（几何）
    public Map<String, MultiLink> multiLinkCollection = Collections.synchronizedMap(new HashMap<String, MultiLink>()); // 道路集合
    
    //----------------从表示路网边的shape文件中构建几何与拓扑路网 --------------------//
    public Network(String edgeFileName, boolean directed) throws ShapefileException, IOException{
    	this.createByNetworkFiles(edgeFileName, directed);
    }
    
    public Network(String edgeFileName, String trafficFileName) throws IOException, ShapefileException {
            this.createByNetworkFiles(edgeFileName, true);//几何、拓扑
            //转向限制设定
            this.initTurnMatrix(); 
            if(trafficFileName != null)
            	this.addTrafficRestriction(trafficFileName); 
    }
    
    //几何拓扑网络的构建
    private void createByNetworkFiles(String roadEdgeFileURL, boolean directed) throws IOException, ShapefileException {
    	//读取Shape      
		FileDataStore readDataStore = FileDataStoreFinder.getDataStore(new File(roadEdgeFileURL));
		SimpleFeatureCollection featureCollection = readDataStore.getFeatureSource().getFeatures();
		SimpleFeatureIterator iterator = featureCollection.features();
		SimpleFeature feature = null;
		
	    //属性列
	    String edgeId = null;
	    String startNodeId = null;
	    String endNodeId = null;
	    int direction = 0;
	    float length = 0; //由于投影后的坐标误差，这里使用实时计算的长度
	    LineString line = null;
	    
/*	    String pathno = null;
	    String mlinkid = null;
	    float soffset = 0;
	    float eoffset = 0;*/
	    
	    //新创建的边、节点集合
	    Edge newEdge = null;
	    Node startNode = null;
	    Node endNode = null;
		
		while(iterator.hasNext()){
			feature = iterator.next();	
			
			edgeId = (String)feature.getAttribute("ID");
			
			startNodeId = (String)feature.getAttribute("SNODEID"); // 数字化起点
			
			endNodeId = (String)feature.getAttribute("ENODEID"); // 数字化终点
			
			if(feature.getAttribute("DIRECTION")!= null)
				direction = Integer.parseInt((String)feature.getAttribute("DIRECTION")); //行车方向
			
			if(feature.getAttribute("LENGTH") != null)
				length = Float.parseFloat((String)feature.getAttribute("LENGTH"))* 1000;
			
			line = (LineString) ((MultiLineString)feature.getDefaultGeometry()).getGeometryN(0);
			
			line.apply(new CoordinateFilter (){
				public void filter(Coordinate coord) {
					coord.x = Math.floor(coord.x*1000)/1000;
					coord.y = Math.floor(coord.y*1000)/1000;					
				}});
									
		    //创建边
/*			if(mlinkid != null)
				newEdge = new Edge(edgeId, startNodeId, endNodeId, direction, line, pathno, mlinkid, soffset, eoffset);
			*/
		    
			newEdge = new Edge(edgeId, startNodeId, endNodeId, direction, line);
			
		    edgeCollection.put(edgeId, newEdge); // 几何路网
		            
		    //创建节点
		    startNode = (Node) nodeCollection.get(startNodeId);
		    if (startNode == null) {
		    	startNode = new Node(startNodeId, line.getStartPoint());
		        nodeCollection.put(startNodeId, startNode);
		    }
		    
		    endNode = (Node) nodeCollection.get(endNodeId);
		    if (endNode == null) {
		    	endNode = new Node(endNodeId, line.getEndPoint());
		        nodeCollection.put(endNodeId, endNode);
		    } 
		    
		    //构建邻接表（拓扑）
		    if(directed){//有向路网 （0：未调查,  1：双向  , 2：顺方向 3：逆方向）
		    	if(direction == 0 || direction == 1){
		    		
				    startNode.addEdge(newEdge);				  
				    endNode.addEdge(newEdge); 
				    
		    	}else if(direction == 2) {
		    		
				    startNode.addEdge(newEdge); 
				    
		    	}else if(direction == 3) {
		    		
				    endNode.addEdge(newEdge); 
		    	}
		 	
		    }else { //无向		    
			    startNode.addEdge(newEdge); 
			    endNode.addEdge(newEdge); 
		    }
		}		
		iterator.close();
    }
    
    //在节点转向矩阵中加入交通限制(简单路口)
    private void addTrafficRestriction(String cNodeFileName) throws
            FileNotFoundException, ShapefileException, IOException {
/*      FileChannel nodeDbIn = new FileInputStream(cNodeFileName + ".dbf").getChannel();
        DbaseFileReader nodeDbr = null;//new DbaseFileReader(nodeDbIn); //属性
        Object[] nodeData = null;
        int nodeIdColumn = selectedDbColumn(nodeDbr, "ID");
        int inEdgeIdColumn = selectedDbColumn(nodeDbr, "INTLINKID");
        int outEdgeIdColumn = selectedDbColumn(nodeDbr, "OUTLINKID");
        String nodeId = null; //节点
        String inEdgeId = null; //进入路段
        String outEdgeId = null; //离开路段
        while (nodeDbr.hasNext()) {
            nodeData = nodeDbr.readEntry();
            nodeId = (String) nodeData[nodeIdColumn];
            inEdgeId = (String) nodeData[inEdgeIdColumn];
            outEdgeId = (String) nodeData[outEdgeIdColumn];
            this.nodeCollection.get(nodeId).addNoTurn(inEdgeId, outEdgeId); //加入转向
        }
        nodeDbr.close();*/
    }
    
    //初始化节点转向矩阵
    private void initTurnMatrix() {
        for (Node node : nodeCollection.values()) {
            int numofEdge = node.getDegree();
            float[][] turnMatrix = new float[numofEdge][numofEdge];
            for (int i = 0; i < numofEdge; i++) {
                for (int j = 0; j < turnMatrix[i].length; j++) {
                	turnMatrix[i][j] = 0; //将所有的转向统计归零
//                    if (j == i) {
//                        turnMatrix[i][j] = 0; //静止U-turn的转向
//                    } else {
//                        turnMatrix[i][j] = 1;
//                    }
                }
            }
            node.turnMatrix = turnMatrix;
        }
    }
    
    public void initTurnMatrixByDB(String tableName, Connection con) throws SQLException{
		//SQL准备
    	String selectSQL = "SELECT * FROM " + tableName + " WHERE nodeid = ? and period = 'Day'";
    	PreparedStatement selectState = con.prepareStatement(selectSQL);
    	ResultSet result = null;
    	String fromEdgeId = null;
    	String toEdgeId = null;
    	float probility = 0.0f;
    	
    	//节点遍历
		for(Node node : this.nodeCollection.values()){
			selectState.setString(1, node.id);
			result = selectState.executeQuery();
			while(result.next()){
				fromEdgeId = result.getString(2);
				toEdgeId = result.getString(3);
				probility = result.getFloat(5);
				node.setTurnProbility(fromEdgeId, toEdgeId, probility);
			}
		}
    }
    
    //--------------------------------基本操作-------------------------------//
    
    //加入节点
    public void addNode(Node node){
    	this.nodeCollection.put(node.id, node); 	
    }
    
    public void addEdge(Edge edge) {
    	
    }
    
    //判断起点
    public boolean isStartNode(String nodeId, String edgeId) {
        return this.edgeCollection.get(edgeId).isStartNode(nodeId);
    }

    //获取节点
    public Node getNodeById(String nodeId) {
        return this.nodeCollection.get(nodeId);
    }

    //获取几何边
    public Edge getEdgeById(String edgeId) {
        return this.edgeCollection.get(edgeId);
    }
    
    //获取邻近节点
    public Node[] findAdjNodes(String nodeId) {
        Node current_Node = this.getNodeById(nodeId);
        String[] nodeIds = current_Node.getAdjNodes();
        Node[] nodes = new Node[nodeIds.length];
        for (int i = 0; i < nodes.length; i++) {
            nodes[i] = this.nodeCollection.get(nodeIds[i]);
        }
        return nodes;
    }
    
    //获取转向方向
	public String getTurnDirection(String fromEdgeId, String toEdgeId){
		//com.vividsolutions.jts.algorithm.Angle.angleBetweenOriented(tip1, tail, tip2);
		return null;
	}
        
	//获取两条边的交点
	public String getCommonPoint(String edgeID1, String edgeID2) {
		Edge edge1 = this.getEdgeById(edgeID1);
		Edge edge2 = this.getEdgeById(edgeID2);
		
		if(edge1.startNodeID.equals(edge2.startNodeID))
			return edge1.startNodeID;	
		else if(edge1.startNodeID.equals(edge2.endNodeID))
			return edge1.startNodeID;
		else if(edge1.endNodeID.equals(edge2.startNodeID))
			return edge1.endNodeID;		
		else if(edge1.endNodeID.equals(edge2.endNodeID))
			return edge1.endNodeID;
		else 
			return null;
	}
	
	//计算线性距离
	public float linearDistance(String edgeID1, String edgeID2, String mLinkID){
		Edge edge1 = this.getEdgeById(edgeID1);
		Edge edge2 = this.getEdgeById(edgeID2);

		return Math.min(Math.abs(edge1.getLinearStartOffest(mLinkID) - edge2.getLinearEndOffset(mLinkID)), 
				Math.abs(edge1.getLinearEndOffset(mLinkID) - edge2.getLinearStartOffest(mLinkID)));

	}
	
	//利用线性参照获取点(以道路起始方向为参照点)
	public Point getPointByLinearReference(String mLinkID, float offsetDistance){
		MultiLink mLink = this.multiLinkCollection.get(mLinkID);
		
		LengthIndexedLine indexedLine = new LengthIndexedLine(mLink.line.getGeometryN(0));
			
		Coordinate coord = indexedLine.extractPoint(offsetDistance);
		
		GeometryFactory factory = new GeometryFactory();
		
		Point point = factory.createPoint(coord);
		
		return point;
	}

	//利用线性参照获取线
	public LineString getLineByLinearReference(String mLinkID, float startOffset, float endOffset){
		MultiLink mLink = this.multiLinkCollection.get(mLinkID);
		
		LengthIndexedLine indexedLine = new LengthIndexedLine(mLink.line.getGeometryN(0));
			
		LineString line = (LineString)indexedLine.extractLine(startOffset, endOffset).getGeometryN(0);
		
		return line;
		
	}
	
	//获取道路
	public MultiLink getMultiLinkByID(String mLinkID){
		return this.multiLinkCollection.get(mLinkID);
	}
	
    //-------------------------基于MultiLink的线性参照网络-------------------//
	
	//利用道路的起始点，生成线性参考的网络
	public void generateLinearNetworkByMultiLink(String roadInfoURL) throws IOException, SchemaException{
		BufferedReader reader = new BufferedReader(new FileReader(roadInfoURL)); //读取道路的起始节点文件
		
		String line = reader.readLine();
		String[] dataEntry = null;
		MultiLink mLink = null;
			
		RoutePlanning rp = new RoutePlanning(this);
		while(line != null){
			dataEntry = line.split(",");
			mLink = new MultiLink(dataEntry[0].trim(), dataEntry[1].trim(), dataEntry[2].trim(), dataEntry[3].trim(), dataEntry[4].trim()); 
			this.linearReferencing(mLink, rp);
			multiLinkCollection.put(mLink.id, mLink);
			line = reader.readLine();
		}
	}

	//获取由MultiLink组成的路网
	public void getLinearNetworkShapefileByMultiLink() throws IOException, SchemaException{
		//生成MultiLink的路网shapefile
		JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");	
	    chooser.setDialogTitle("Save a new shapefile");
	    chooser.setSaveFile(new File("D:\\"));
        if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) {
            return;
        }
        File outputShapefile = chooser.getSelectedFile();
     
		ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
		
		Map<String, Serializable> params = new HashMap<String, Serializable>();
		params.put("url", outputShapefile.toURI().toURL());
		params.put("create spatial index", Boolean.TRUE);
		
		ShapefileDataStore writeDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);

		// specify the feature type
		final SimpleFeatureType TYPE = DataUtilities.createType(
				"MultiLink",
				"GEOM:MultiLineString:srid=4326," 
			    + "ID:String,"
				+ "ROADID:String,"
				+ "RNAME:String,"
				+ "DIRECTION:String,"
				+ "SNODE:String,"
				+ "ENODE:String"			    
				); //+ "DIRECTION:int,"

		writeDataStore.createSchema(TYPE);
		writeDataStore.forceSchemaCRS(DefaultGeographicCRS.WGS84);

		//prepare new feature collection	
		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);

		SimpleFeature feature = null;
		
		//		
		for (MultiLink mLink : this.multiLinkCollection.values()) {
			// create feature	
			featureBuilder.add(mLink.line);
			featureBuilder.add(mLink.id);
			featureBuilder.add(mLink.roadID);
			featureBuilder.add(mLink.roadName);
			featureBuilder.add(mLink.direction);
			featureBuilder.add(mLink.startNodeID);
			featureBuilder.add(mLink.endNodeID);
			feature = featureBuilder.buildFeature(null);
			collection.add(feature);
		}

		//create transaction
		Transaction transaction = new DefaultTransaction("create");
		String typeName = writeDataStore.getTypeNames()[0];
		SimpleFeatureSource featureSource = writeDataStore.getFeatureSource(typeName);

		if (featureSource instanceof SimpleFeatureStore) {
			SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
			featureStore.setTransaction(transaction);
			try {
				featureStore.addFeatures(collection);
				transaction.commit();
			} catch (Exception problem) {
				problem.printStackTrace();
				transaction.rollback();
			} finally {
				transaction.close();
			}
			System.exit(0); // success!
		} else {
			System.out.println(typeName + " does not support read/write access");
			System.exit(1);
		}
	}
	
	//获取由LinearLink组成的路网
	public void getLinearNetworkShapefileByLink() throws IOException, SchemaException{
		//生成MultiLink的路网shapefile
		JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");	
	    chooser.setDialogTitle("Save a new shapefile");
	    chooser.setSaveFile(new File("D:\\"));
        if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) {
            return;
        }
        File outputShapefile = chooser.getSelectedFile();
     
		ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
		
		Map<String, Serializable> params = new HashMap<String, Serializable>();
		params.put("url", outputShapefile.toURI().toURL());
		params.put("create spatial index", Boolean.TRUE);
		
		ShapefileDataStore writeDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);

		// specify the feature type
		final SimpleFeatureType TYPE = DataUtilities.createType(
				"LinearLink",
				"GEOM:MultiLineString:srid=4326," 
			    + "ID:String,"
				+ "SNODEID:String,"
				+ "ENODEID:String,"
				+ "DIRECTION:int,"
				+ "MLINKID:String,"
				+ "SOFFSET:float,"
				+ "EOFFSET:float,"
				); //+ "DIRECTION:int,"

		writeDataStore.createSchema(TYPE);
		writeDataStore.forceSchemaCRS(DefaultGeographicCRS.WGS84);

		//prepare new feature collection	
		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);

		SimpleFeature feature = null;
		
		//		
		for (Edge edge : this.edgeCollection.values()) {
			// create feature	
			featureBuilder.add(new GeometryFactory().createMultiLineString(new LineString[]{edge.line}));
			featureBuilder.add(edge.id);
			featureBuilder.add(edge.startNodeID);
			featureBuilder.add(edge.endNodeID);
			featureBuilder.add(edge.direction);
			featureBuilder.add(edge.multiLinkID1);
			featureBuilder.add(edge.startOffset1);
			featureBuilder.add(edge.endOffset1);
			feature = featureBuilder.buildFeature(null);
			collection.add(feature);
			if(edge.multiLinkID2 != null) {
				featureBuilder.add(new GeometryFactory().createMultiLineString(new LineString[]{edge.line}));
				featureBuilder.add(edge.id);
				featureBuilder.add(edge.startNodeID);
				featureBuilder.add(edge.endNodeID);
				featureBuilder.add(edge.direction);
				featureBuilder.add(edge.multiLinkID2);
				featureBuilder.add(edge.startOffset2);
				featureBuilder.add(edge.endOffset2);
				feature = featureBuilder.buildFeature(null);
				collection.add(feature);
			}
		}

		//create transaction
		Transaction transaction = new DefaultTransaction("create");
		String typeName = writeDataStore.getTypeNames()[0];
		SimpleFeatureSource featureSource = writeDataStore.getFeatureSource(typeName);

		if (featureSource instanceof SimpleFeatureStore) {
			SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
			featureStore.setTransaction(transaction);
			try {
				featureStore.addFeatures(collection);
				transaction.commit();
			} catch (Exception problem) {
				problem.printStackTrace();
				transaction.rollback();
			} finally {
				transaction.close();
			}
			System.exit(0); // success!
		} else {
			System.out.println(typeName + " does not support read/write access");
			System.exit(1);
		}
	}
 	
	//进行线性参考
	private void linearReferencing(MultiLink mLink , RoutePlanning rp){//利用道路的起始点计算最短路径，生成MultiLink,生成OFFSET
		rp.computeFastestWay(this.getNodeById(mLink.startNodeID), this.getNodeById(mLink.endNodeID));		
		ArrayList<Edge> route = rp.getRoute();
		ArrayList<LineString> lineList = new ArrayList<LineString>();
		ArrayList<String> edgeIDList = new ArrayList <String>();
		
		Edge edge = null;
		float offset = 0.0f;
		
		LineMerger merger = new LineMerger();
		
		for(int i = 0; i < route.size(); i++){			
			edge = route.get(i);
			edge.setLinearReference(mLink.id, offset, offset + (float) edge.length); // 生成线性参照
			offset += (float) edge.length;
			edgeIDList.add(edge.id);
			lineList.add(edge.line);
			mLink.length += edge.length;
		}
		
		merger.add(lineList);
		
//		if(mLink.id.equals("25201"))
//			System.out.println(merger.getMergedLineStrings());
		     				
		LineString[] lines = (LineString[]) merger.getMergedLineStrings().toArray(new LineString[]{});
			
		double digitalAngle = this.getDirectedAngle(lines[0].getStartPoint(), lines[0].getEndPoint());
		
		double travelAngle = this.getDirectedAngle(this.getNodeById(mLink.startNodeID).point, this.getNodeById(mLink.endNodeID).point);
		
		if(Math.abs(digitalAngle - travelAngle) > 5E-1){
			//System.out.println("HERE" + mLink.id);
			lines[0] = (LineString) lines[0].reverse();
		}	
		
		mLink.line = new MultiLineString(lines, new GeometryFactory()); //生成MultiLink

		mLink.edgeIDList = edgeIDList;
	}
		
	private double getDirectedAngle(Point startPoint, Point endPoint) {
		double angle = Math.acos((endPoint.getX() - startPoint.getX())/startPoint.distance(endPoint));
		
		if(endPoint.getY() - startPoint.getY() < 0)
			return 2*Math.PI - angle;
		
		return angle;
	}
	
	//-----------------------------------------------------------------------//
	
    public static void main(String[] args) throws IOException, ShapefileException, SQLException, ClassNotFoundException, SchemaException {
        Network road = new Network("D:\\Data\\Road\\Wuhan_2010.9\\WuhanMainRoad_3rd_PATHNAME_wuchang6_EPSG32650.shp", true);
             		
        //road.generateLinearNetworkByMultiLink("d:\\road.txt"); 
        
/*        for(Edge edge : road.edgeCollection.values()){
        	System.out.println("edge:" + edge.id );
        }*/
/*        
        for(String mLinkID : road.multiLinkCollection.keySet()){
        	System.out.println("MultiLinkID:" + road.multiLinkCollection.get(mLinkID).edgeIDList);
        }*/
        
       //road.getLinearNetworkShapefileByMultiLink();
        
/*        MultiLink mLink = road.getMultiLinkByID("25201");
        
        System.out.println("Mlink Length:" + mLink.length);
        
        road.getLineByLinearReference("25201", 2917.2183f, 3359.5842f);*/
        
        System.out.println("node num:" + road.nodeCollection.size() + "Edge num:" + road.edgeCollection.size());
                 
    }

}
